/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions;

import com.softwarecraftsmen.inputStreamReaders.ManifestInputStreamReader;
import com.softwarecraftsmen.orogeny.actions.manifest.ManifestAttributesManipulator;
import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.BuildLog;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.Summary;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Failure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.AbsolutePath;
import com.softwarecraftsmen.orogeny.filing.AbsolutePaths;
import com.softwarecraftsmen.orogeny.filing.CouldNotMakeAllFoldersNecessaryException;
import com.softwarecraftsmen.orogeny.filing.IsNotADirectoryException;
import static com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter.ZipOrJar;
import com.softwarecraftsmen.outputStreamWriters.ManifestOutputStreamWriter;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import static java.lang.String.format;
import static java.util.Locale.UK;
import java.util.jar.Manifest;

public class ManifestAction extends AbstractAction
{
	private final AbsoluteFile manifestFile;
	private final AbsolutePaths classPath;
	private final String mainClassName;

	private ManifestAction(final @NotNull AbsoluteFile manifestFile, final @NotNull AbsolutePaths classPath, final @NotNull String mainClassName)
	{
		this.manifestFile = manifestFile;
		this.classPath = classPath;
		this.mainClassName = mainClassName;
	}

	@NotNull
	public String describeWhatWeDo()
	{
		return "manifest";
	}

	@SuppressWarnings({"ReturnInsideFinallyBlock"})
	@NotNull
	public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		final Manifest manifest;
		if (manifestFile.exists())
		{
			buildLog.writeMessage(Summary, format(UK, "Loading existing manifest file %1$s", manifestFile));
			try
			{
				manifest = manifestFile.readData(new ManifestInputStreamReader());
			}
			catch (IOException e)
			{
				return Failure(e);
			}
		}
		else
		{
			buildLog.writeMessage(Summary, format(UK, "Creating new manifest"));
			manifest = new Manifest();
		}

		final ManifestAttributesManipulator manifestAttributes = new ManifestAttributesManipulator(manifest, buildLog);
		manifestAttributes.ensureBasicAttributes(buildEnvironment);
		manifestAttributes.putClassPath(classPath.findFiles(ZipOrJar));
		manifestAttributes.putMainClassName(mainClassName);

		buildLog.writeMessage(Summary, format(UK, "Writing manifest file %1$s", manifestFile));
		try
		{
			manifestFile.writeData(new ManifestOutputStreamWriter(manifest));
		}
		catch (IOException e)
		{
			return Failure(e);
		}
		catch (CouldNotMakeAllFoldersNecessaryException e)
		{
			return Failure(e);
		}
		catch (IsNotADirectoryException e)
		{
			return Failure(e);
		}
		return Success;
	}

	@NotNull
	public static SpecifyOutputDirectory manifest(final @NotNull AbsoluteDirectory outputDirectory)
	{
		return new SpecifyOutputDirectory(outputDirectory.subDirectory("META-INF").file("MANIFEST.MF"));
	}

	@NotNull
	public static SpecifyOutputDirectory manifest(final @NotNull AbsoluteFile manifestFile)
	{
		return new SpecifyOutputDirectory(manifestFile);
	}

	public static final class SpecifyOutputDirectory
	{
		private final AbsoluteFile manifestFile;
		private static final AbsolutePath[] EmptyClassPath = new AbsolutePath[] {};

		private SpecifyOutputDirectory(final @NotNull AbsoluteFile manifestFile) {this.manifestFile = manifestFile;}

		@NotNull
		public SpecifyClassPath withClassPath(final @NotNull AbsolutePath... classPath)
		{
			return withClassPath(absoluteDirectoriesAndFiles(classPath));
		}

		@NotNull
		public SpecifyClassPath withClassPath(final @NotNull AbsolutePaths... classPath)
		{
			return new SpecifyClassPath(absoluteDirectoriesAndFiles(classPath));
		}

		@NotNull
		public SpecifyClassPath withoutClassPath()
		{
			return withClassPath(EmptyClassPath);
		}

		public final class SpecifyClassPath extends AbstractAction
		{
			private final AbsolutePaths classPath;

			@NotNull
			public static final String NoMainClassName = "";

			private SpecifyClassPath(final @NotNull AbsolutePaths classPath)
			{
				this.classPath = classPath;
			}

			@NotNull
			public String describeWhatWeDo()
			{
				return new ManifestAction(manifestFile, classPath, NoMainClassName).describeWhatWeDo();
			}

			@NotNull
			public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
			{
				return new ManifestAction(manifestFile, classPath, NoMainClassName).execute(buildLog, buildEnvironment);
			}

			@NotNull
			public ManifestAction withMainClass(final @NotNull String mainClassName)
			{
				if (mainClassName.endsWith(".class"))
				{
					throw new IllegalArgumentException(format(UK, "A class name (such as %1$s) can not end with '.class'. An example would be com.softwarecraftsmen.executable.ConsoleEntryPoint", mainClassName));
				}
				if (mainClassName.indexOf("/") != -1)
				{
					throw new IllegalArgumentException(format(UK, "A class name (such as %1$s) can not contain '/'. An example would be com.softwarecraftsmen.executable.ConsoleEntryPoint", mainClassName));
				}
				return new ManifestAction(manifestFile, classPath, mainClassName);
			}

			@NotNull
			public ManifestAction withoutMainClass()
			{
				return new ManifestAction(manifestFile, classPath, "");
			}
		}
	}
}